﻿#include "Level.h"

#include "App/Window.h"
#include "Util/Random.h"
#include "Scene/Effect/CrosshairEffect.h"
#include "Render/ColorRenderPass.h"
#include "Behavior/BlockBreakParticleBehavior.h"
#include "Behavior/LightChangeBehavior.h"

Level::Level(const std::string &saveName)
    : m_persistence(CreateRef<Persistence>(saveName)),
      m_world(CreateRef<World>(*this, m_persistence,
                               std::vector{std::static_pointer_cast<WorldBehavior>(CreateRef<BlockBreakParticleBehavior>()),
                                           //std::static_pointer_cast<WorldBehavior>(CreateRef<LightChangeBehavior>())
                                          })),
      m_player(CreateRef<Player>(m_world, m_persistence)),
      m_blockRegistry(m_persistence->GetBlockRegistry())
{
  m_skybox.SetTime(m_persistence->GetTime());
  AddPostProcessEffects();
}

Level::~Level()
{
  m_persistence->CommitBlockRegistry(m_blockRegistry);
  m_persistence->CommitTime(m_skybox.GetTime());
}

void Level::Update(float deltaTime)
{
  m_deltaTime = deltaTime;
  m_player->Update(deltaTime);
  m_world->Update(m_player->GetPosition(), deltaTime);
  m_skybox.Update(
      m_player->GetCamera()->GetProjectionMatrix(),
      m_player->GetCamera()->GetViewMatrix(),
      deltaTime);
}

void Level::Render()
{
  m_skybox.Render();

  const int32_t width = Window::GetInstance().GetWidth();
  const int32_t height = Window::GetInstance().GetHeight();

  // const uint32_t SHADOW_W = 1024;
  // const uint32_t SHADOW_H = 1024;
  // if (m_fbShadow == nullptr)
  //   m_fbShadow = CreateRef<FrameBuffer>(SHADOW_W, SHADOW_H, true, 0);
  // Window::GetInstance().GetFrameBufferStack()->Push(m_fbShadow);

  if (m_fbOpaque == nullptr || m_fbOpaque->DismatchSize(width, height))
    m_fbOpaque = CreateRef<FrameBuffer>(width, height, true, 4);
  Window::GetInstance().GetFrameBufferStack()->Push(m_fbOpaque); // 1,2,3
  glClearColor(0, 0, 0, 0);
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
  glEnable(GL_BLEND);
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  m_world->RenderOpaque(m_player->GetCamera(), m_player->GetPosition());
  glDisable(GL_BLEND);
  Window::GetInstance().GetFrameBufferStack()->Pop();

  if (m_fbLightedOpaque == nullptr || m_fbLightedOpaque->DismatchSize(width, height))
    m_fbLightedOpaque = CreateRef<FrameBuffer>(width, height, false, 1);
  Window::GetInstance().GetFrameBufferStack()->Push(m_fbLightedOpaque); // 4
  glClearColor(0, 0, 0, 0);
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
  glEnable(GL_BLEND);
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  m_world->RenderDeferredLight(m_player->GetCamera(), m_player->GetPosition(), m_skybox, m_fbOpaque, m_fbOpaque);
  glDisable(GL_BLEND);
  Window::GetInstance().GetFrameBufferStack()->Pop();

  if (m_fbTransparent == nullptr || m_fbTransparent->DismatchSize(width, height))
    m_fbTransparent = CreateRef<FrameBuffer>(width, height, false, 2); // 5,6
  Window::GetInstance().GetFrameBufferStack()->Push(m_fbTransparent);
  glEnable(GL_BLEND);
  glBlendFunci(0, GL_ONE, GL_ONE);
  glBlendFunci(1, GL_ZERO, GL_ONE_MINUS_SRC_ALPHA);
  glClearColor(0, 0, 0, 0);
  glClear(GL_COLOR_BUFFER_BIT);
  m_fbTransparent->ClearColorAttachment(1, glm::vec4(1));
  m_world->RenderTransparent(m_player->GetCamera(), m_player->GetPosition(), m_fbOpaque, m_fbOpaque);
  glDisable(GL_BLEND);
  Window::GetInstance().GetFrameBufferStack()->Pop();

  glEnable(GL_BLEND);
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  static auto blendShader = AssetManager::LoadAsset<Shader>("assets/shaders/src/BlendShader");
  ColorRenderPass renderPass(blendShader);
  renderPass.SetTexture("uAccumTexture", m_fbTransparent->GetColorAttachment(0), 1);
  renderPass.SetTexture("uRevealageTexture", m_fbTransparent->GetColorAttachment(1), 2);
  renderPass.SetTexture("uOpaqueTexture", m_fbLightedOpaque->GetColorAttachment(0), 3);
  blendShader->SetVec3("uLight.direction", m_skybox.GetSunshineDirection());
  blendShader->SetVec4("uLight.color", m_skybox.GetSunshineColorByTime());
  blendShader->SetFloat("uTime", m_skybox.GetTimeOfDay());
  renderPass.Render();
  glDisable(GL_BLEND);

  if (m_player->HasRayCastHit())
  {
    auto target = m_player->GetHitTarget();
    Ref<BlockModel> model = target.blockType->model;
    if (model && model != m_blockOutline.GetBlockModel())
    {
      m_blockOutline.SetBlockModel(model);
    }
    const glm::mat4 mvp = m_player->GetCamera()->GetViewProjectionMatrix();
    m_blockOutline.Render(mvp * glm::translate(glm::vec3(m_player->GetHitTarget().blockPosition)));
  }

  for (auto &effect : m_postProcessEffects)
  {
    effect->Render();
  }
}

void Level::RenderGui()
{
  if (!m_isOpenGui)
    return;
}

void Level::AddPostProcessEffects()
{
  m_postProcessEffects.push_back(CreateRef<CrosshairEffect>(true));
}
